#  Copyright 2011 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from erlastic import Atom


class ASN1Factory(object):

    def __init__(self, records):
        self._records = records

    def parse(self, value):
        if isinstance(value, tuple):
            return self._tuple(value)
        if isinstance(value, list):
            return self._list(value)
        if isinstance(value, Atom):
            return self._primitive(value)
        if isinstance(value, bool):
            return self._primitive(value)
        if isinstance(value, int):
            return self._primitive(value)
        raise AssertionError('Unknown value type: %s' % value)

    def _tuple(self, value):
        type = value[0]
        if not isinstance(type, Atom):
            raise AssertionError('Unknown first value for tuple: %s' % value)
        if str(type)[0].islower():
            return self._choice(value)
        if str(type)[0].isupper():
            return self._record(value)
        raise AssertionError('Unknown first value atom for tuple: %s' % value)

    def _choice(self, value):
        if len(value) is not 2:
            raise AssertionError('Illegal value for choice: %s' % value)
        return _ASN1Choice(value[0], self.parse(value[1]))

    def _record(self, value):
        name = str(value[0])
        if name not in self._records:
            raise AssertionError('Record %s not found.' % name)
        return _ASN1Record(name, self._records[name], [self.parse(element) for element in value[1:]])

    def _list(self, value):
        return _ASN1List([self.parse(element) for element in value])

    def _primitive(self, value):
        return _ASN1Primitive(value)


class _ASN1Element(object):

    is_char = False
    no_val = False
    complex_value = True

    @property
    def list(self):
        raise AssertionError('Value not list. %s' % str(self))

    def _indent(self, value):
        indented = ''.join([('  ')+'%s' % row for row in value.splitlines(True)])
        return indented[2:]

    def _empty(self, path):
        return not path or len(path)==1 and not path[0]

    def _quote_atom(self, atom):
        if atom and (atom[0].isupper() or '-' in atom):
            return "'%s'" % atom
        return atom

    def __str__(self):
        return self.record


class _ASN1Primitive(_ASN1Element):

    complex_value = False

    def __init__(self, value):
        self.value=value

    @property
    def int(self):
        if not isinstance(self.value, int):
            raise AssertionError('Primitive value not int. %s' % type(self.value))
        return self.value

    @property
    def bool(self):
        if not isinstance(self.value, bool):
            raise AssertionError('Primitive value not boolean. %s' % type(self.value))
        return self.value

    @property
    def record(self):
        if isinstance(self.value, bool):
            return 'true' if self.value else 'false'
        if isinstance(self.value, Atom):
            return self._quote_atom(self.string)
        return str(self.value)

    @property
    def is_char(self):
        if not isinstance(self.value, int):
            return False
        # FIXME: check what are allowed character values?
        return 31 < self.value < 127 and self.value is not 34

    @property
    def erlastic(self):
        return self.value

    @property
    def no_val(self):
        return self.value == Atom('asn1_NOVALUE')

    @property
    def string(self):
        return str(self.value)

    def path(self, path):
        if self._empty(path):
            return self
        raise AssertionError('Illegal path %s on primitive %s' % ('.'.join(path), self.value))

    def set(self, value):
        if isinstance(self.value, Atom):
            self.value = Atom(value)
        elif isinstance(self.value, bool):
            self.value = bool(value)
        elif isinstance(self.value, int):
            self.value = int(value)
        else:
            raise AssertionError('Illegal value %s for element %s' % (value, self.value))


class _ASN1Choice(_ASN1Element):

    def __init__(self, name, value):
        self.name = name
        self.value = value

    @property
    def record(self):
        return self._indent('{%s, %s}' % (self._quote_atom(self.name), self.value.record))

    @property
    def erlastic(self):
        return (Atom(self.name), self.value.erlastic)

    def path(self, path):
        if self._empty(path):
            return self
        return self.value.path(path)


class _ASN1List( _ASN1Element):

    def __init__(self, values):
        self.values = values

    def __getitem__(self, key):
        return self.values[key]

    @property
    def string(self):
        return "".join([chr(elem.int) for elem in self.values])

    @property
    def list(self):
        return self.erlastic

    @property
    def record(self):
        if self.is_string:
            return '"%s"' % self.string
        elif any([val.complex_value for val in self.values]):
            return self._indent('[\n'+',\n'.join([val.record for val in self.values])+']')
        else:
            return '['+', '.join([val.record for val in self.values])+']'

    @property
    def is_string(self):
        return all([elem.is_char for elem in self.values])

    @property
    def erlastic(self):
        return [elem.erlastic for elem in self.values]

    def path(self, path):
        if self._empty(path):
            return self
        return self[int(path[0])].path(path[1:])

    def set(self, value):
        self.values = [_ASN1Primitive(ord(char)) for char in value]


class _ASN1Record( _ASN1Element):

    def __init__(self, name, fields, values):
        self.name = name
        if len(fields) is not len(values):
            raise AssertionError('Number of fields (%d) and values (%d) do not match on record %s' % (len(fields), len(values), name))
        self.fields = fields
        self.values = values

    def __getitem__(self, key):
        return self.values[self.fields.index(key)]

    def __contains__(self, key):
        return key in self.fields

    def path(self, path):
        if self._empty(path):
            return self
        return self[path[0]].path(path[1:])

    @property
    def record(self):
        value = "#%s {\n" % self._quote_atom(self.name)
        value += ',\n'.join('%s = %s' % (self._quote_atom(key), val.record)
                            for key, val in zip(self.fields, self.values)
                            if not val.no_val)
        value += '}'
        return self._indent(value)

    @property
    def erlastic(self):
        return tuple([Atom(self.name)]+[elem.erlastic for elem in self.values])
